查看原文
其他

优化| APK瘦身最佳实践

2017-08-02 TonnyL 开发者技术前线

点击标题下「蓝色微信名」可快速关注            


APK瘦身也是anroid领域比较关注的技术之一,在开始对APK进行正式的减肥之前,我们先来了解一下APK包的构成,这样才好对症下药嘛。知己知彼方能百战不殆。



APK文件内容速揽


我们可以通过 unzip <apkname>.apk 命令实现对apk的解压(你也可以直接将<apkname>.apk更名为<apkname>.zip后直接通过解压软件解压)。解压后,我们就会得到一个文件夹了,里面包含了:

AndroidManifest.xml/assetsclasses.dex
classes2.dex.../lib
   /arm64-v8a
   /armeabi-v7a
   /mips
   /x86
   /x86_64/META-INF
   CERT.RSA
   CERT.SF
   MANIFEST.MF/res
   /anim
   ...resources.arsc

各个文件和目录的作用是什么呢?


文件/目录


作用



AndroidManifest.xml

清单文件,列出了应用的名称,版本,权限等信息。

assets

配置文件,放置一些本地资源,例如本地html,可以通过使用AssetManager检索。

classes.dex, classes2.dex

编译后的字节码文件,能够被Dalvik/ART理解。

lib

存放特定平台使用的编译后的.so文件,每一个子文件夹对应一个特定的平台,例如armeabiarmeabi-v7aarm64-v8ax86x86_64, and mips.

META-NIF

包含 CERT.RSACERT.SFMANIFEST.MF, 存放的签名信息,用于保证系统的安全性和APK文件的完整性。

res

存放没有被编译到 resources.arsc文件中的资源文件。

resouces.arsc

编译后的二进制资源文件。存放 res/values/ 目录下的所有XML内容。打包工具对XML内容进行提取,编译为二进制,并进行分类,其中,XML内容包含了不同语言的strings和styles,同时还包含没有被直接打包至 resources.arsc 文件的其他文件的路径,例如布局文件和图片文件。

OK,既然我们已经了解了APK的各个组成部分,那么我们就可以针对下面的三个组成部分,采取逐个击破的方式,达到缩减APK体积的目的:

  • Java代码

  • 资源文件

  • Native Code

  • 其他


Java代码

我们可以使用Proguard,在编译时对Java代码进行混淆,优化和压缩。Proguard对代码进行遍历,然后剔除其中未被使用的冗余的代码,并对类,属性,接口等进行重命名,从而达到瘦身的目的。

我们可以在 build.gradle 文件中配置Proguard。

buildTypes {

       release {
           minifyEnabled true
           proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
       }

   }

在未使用Proguard之前,我们的APK体积达到了13.9M。


开启Proguard后,APK的体积缩减到了10.1M。可见,效果还是很明显的。



除此之外,微信的工程师还开源了一个项目,可以直接对APK进行处理,缩小体积。具体的使用方法:https://github.com/shwenzhang/AndResGuard。


资源文件


Lint

由于Proguard只对Java代码起作用,对于资源文件,它就无能为力了。这个时候,Lint就派上用场了。Lint是一个静态的代码分析器,我们可以通过在 Android Studio -> Analyze -> Inspect code... , 选择范围即可开始就检查。


Lint在检查完成后,会提供一份详细的资源文件清单,并且将没有用到的资源在 UnusedResources:Unused resources 区域。只要我们没有通过反射使用这些资源,就可以放心的删掉它们了。



下面是我根据lint的提示,剔除了部分无用资源后,APK的体积:



Notice:需要注意的是,Lint并不会扫描 assets 目录,所以最好还是手动的检查一下 assets 目录下是否有未被使用的文件吧。

我们还可以通过配置 shrinkResources 来移除未使用的资源。不过,使用 shrinkResources 必须开启代码混淆。在处理过程中,ProGuard 会移除未被使用的代码,但是不会移除资源。而开启 shrinkResources 后,Gradle就会移除资源了。

下面为开启 shrinkResources 后APK体积的变化:



又缩小了一点点。


图片压缩

对于资源文件的优化,其实占大头的还是图片。Android打包本身会对png进行无损压缩,在不那么影响图片显示效果的情况下,对图片进行有损压缩对apk体积的减小还是非常可观的。

我们先使用TinyPng对项目中四张比较大的图片进行压缩,可以看出,效果还是非常不错的。

使用TinyPng后,我们还可以将图片转换为 WebP 格式,进一步缩小图片的体积。不过需要注意的是,WebP格式仅适用于Android 4.0+版本,如果你的应用需要兼容Android 2.3,那么需要额外的引入 .so 文件,apk的体积自然也会增加。所以,根据你的需求,权衡利弊吧。

在 Android Studio 中,选定需要转换的图片,然后右键鼠标,即可在菜单底部看到 Convert to WebP 的选项了,然后就可以进行转换的操作。


经过上面的处理,我们的App体积来到了9.6M。



矢量图

如果你的项目中使用 appcompat 兼容包,并且版本在23.2以上,那么,使用矢量图或许是个不错的选择。矢量图和分辨率无关,理论上支持任何级别的缩放。以一张常用的 点赞 图标为例,在 Android中使用 Vector 矢量图,大小为:


而它对应的xxxhdpi的png格式,大小为:


效果显而易见,但是这也并不是意味着,我们就可以将所有的图片替换为 矢量图 了 。我们来看一看 ic_favorite_black_24dp.xml 的源代码:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
       android:width="24dp"
       android:height="24dp"
       android:viewportWidth="24.0"
       android:viewportHeight="24.0">
   <path
       android:fillColor="#FF000000"
       android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"/></vector>

正如你所见,vector的颜色是单一的颜色,所以,vector 适用于图标等颜色单一的图片,如果是颜色比较复杂,那么vector很明显就不合适了。


使用JPG

对于非透明的大图,JPG格式将会比PNG格式的大小有显著的优势,虽然不是绝对的,但是通常会减小到一半都不止。在启动页,活动页等之类的大图展示区采用JPG将是非常明智的选择。

对于图片的使用,Google的建议,简单来说就是:VD->WebP->Png->JPG


  • 如果是纯色的icon,那么用svg

  • 如果是两种以上颜色的icon,用webp

  • 如果webp无法达到效果,选择png

  • 如果图片没有alpha通道,可以考虑jpg



复用图片

项目中我们还可能遇到图片内容相同,仅仅是颜色不同的情况。这个时候我们就可以使用Android提供的着色来完成,而不用提供好几套图片。例如在Android 5.0+上我们可以使用 android:tint 和 android:tintMode,在低版本中可以使用 ColorFilter

对于那些内容颜色等都相同,只是方向不同的图片,我们可以只保留一中方向的,其他方向的图片通过代码实现。例如我们可以对上面提到的 ic_favorite_black_24dp 进行翻转。创建一个 drawable

<?xml version="1.0" encoding="utf-8"?><rotate xmlns:android="http://schemas.android.com/apk/res/android"
   android:drawable="@drawable/ic_favorite_black_24dp"
   android:fromDegrees="180"
   android:pivotX="50%"
   android:pivotY="50%"
   android:toDegrees="180" />

虽然Android设备的分辨率非常的多,但这并不代表着我们需要为每一种分辨率都准备一套资源。在显示差异不大的情况下,我们可以尽量复用一套图片资源,一套布局,然后再考虑特定屏幕密度。


语言资源

说完了图片,我们还要来说说语言资源。对于大多数的应用,并不需要支持几十种的国际化。使用Gradle,对语言资源进行配置,也可以达到应用瘦身的目的。例如,我们的应用如果只需要支持中文和英语:

android {
   defaultConfig {
       ...
       resConfigs "en", "zh"
       // 支持分辨率
       resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
   }
}

Native Code

如果你的App使用到了Native code,在不影响功能的前提下,可以考虑去除部分平台对应的代码,例如移除对 armeabi、mips的支持。

android {
   ...
   splits {
       abi {
           enable true
           reset()
           include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
           universalApk true
       }
   }

}


当然,如果是特别大的原生库,我们还可以通过网络,从云端获取,而不直接打包在APK中。

其他


  • 别忘了最简单,也最容易忽视的,去除重复的依赖,或者是引用更加轻量级的库,也可以达到apk瘦身的效果。

  • 在Release版本中,去除那些只会在debug时才会出现的代码。

  • 对于那些使用频率很小的文件或者是图片,可以存放到云端后,通过网络加载。


结语


相对小的体积能够在用户安装前就给用户留下不错的印象,但是,这不并代表着我们可以为了追求APK体积的小巧而过度的牺牲用户体验。权衡利弊后,选择合适的,才是最重要的。

项目完整地址:https://github.com/TonnyL/Espresso


Espresso is an express delivery tracking app designed with Material Design style, built on MVP(Model-View-Presenter) architecture with RxJava2, Retrofit2, Realm database and ZXing.


作者:TonnyL
链接:http://www.jianshu.com/p/f6b96a25362f



iOS打赏通道





技术 - 资讯 - 感悟

END

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存